#  xmms2.py - XMMS2 service abstraction modules
#  Copyright (C) 2005-2007 Sham Chukoury <eleusis@xmms.org>
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2.1 of the License, or (at your option) any later version.
#
#  This library is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.

from service import Service
from Chalyx.base import ProxiedSignal
from Chalyx.config import Config
import Chalyx
import pwd
import os
import xmmsclient
import xmmsclient.collections
import inspect
import re

class XMMS2Signal(ProxiedSignal):
	"""XMMS2 'proxy' signal"""
	def __init__(self, name, service, remote, handler):
		self.active = False
		self._tokens = []
		self.name = name
		self.remote = remote
		self.handler = handler
		self.lastResult = None
		self.service = service
		self.service.connect("service_connected", self.on_service_connected, None)

	def _do_proxy(self, res):
		self.lastResult = res
		self.handler(res)

	def activate_proxy(self):
		if not self._check_activate(): return
		if not self.service.connected: return
		#print self.name, "activating..."
		self.remote(self._do_proxy)
		self.active = True

	def deactivate_proxy(self):
		if not self._check_deactivate(): return
		if self.lastResult is not None:
			self.lastResult.disconnect()
		self.active = False

	def on_service_connected(self, obj, sigdata, udata):
		connected = sigdata
		if connected and len(self.tokens) > 0:
			self.activate_proxy()
		elif not connected:
			self.deactivate_proxy()

class XMMS2Service(Service):
	def __init__(self, connClsData, clientName, cfgHandler, mname="xmms2-service"):
		Service.__init__(self, cfgHandler, mname)
		self.clientName = clientName
		self.connClass = connClsData[0]
		self.connClsArgs = connClsData[1]
		self.xmms = xmmsclient.XMMS(clientName)
		self.connection = None
		self.connected = False
		
		self.config = cfgHandler["XMMS2Service"]
		self.__initcfg()
		
		self.signals = {
			"service_connected": [],
			"pl_repeat_change": []
		}
		self.signals["pb_playtime"] = XMMS2Signal("pb_playtime", self, self.xmms.signal_playback_playtime, self._playtime)
		self.signals["pb_status"] = XMMS2Signal("pb_status", self, self.xmms.broadcast_playback_status, self._pbstatus)
		self.signals["pb_current_id"] = XMMS2Signal("pb_current_id", self, self.xmms.broadcast_playback_current_id, self._curid)
		self.signals["pl_pos"] = XMMS2Signal("pl_pos", self, self.xmms.broadcast_playlist_current_pos, self._plpos)
		self.signals["pl_change"] = XMMS2Signal("pl_change", self, self.xmms.broadcast_playlist_changed, self._plchg)
		self.signals["config_change"] = XMMS2Signal("config_change", self, self.xmms.broadcast_configval_changed, self._cfgchg)
		self.signals["pl_load"] = XMMS2Signal("pl_load", self, self.xmms.broadcast_playlist_loaded, self._plload)
		self.signals["pl_repeat_change"] = self.signals["config_change"]
		self.__xmmsCfg = {}
		self.connect("service_connected", self.__on_connect, None)

	def __initcfg(self):
		ipcPath = os.getenv("XMMS_PATH")
		if not ipcPath:
			ipcPath = "unix:///tmp/xmms-ipc-%s" % pwd.getpwuid(os.getuid())[0]
		self.config.register("ipcPath", "Path to XMMS2 service", ipcPath)
	
	def __on_connect(self, obj, connected, udata=None):
		def __noop(*args):
			pass
		if connected:
			self.playlist_get_repeat(__noop)
	
	def s_connect(self, address = None):
		# TODO: trap IOError?
		# TODO: must be able to disconnect & reconnect
		# TODO: multiple connections for 'slow' commands, e.g. mlib?
		path = address
		if not path:
			path = self.config["ipcPath"]
		self.xmms.connect(path, self.disconnect_cb)
		try:
			self.connection = self.connClass(self.xmms, self.connClsArgs)
		except TypeError:
			if len(inspect.getargspec(self.connClass.__init__)[0]) == 2:
				self.connection = self.connClass(self.xmms)
			else:
				raise
		self.connected = True
		self.emit("service_connected", self.connected)

	def disconnect_cb(self, xmms):
		# TODO/FIXME: invalid state after deleting self.xmms here
		self.connected = False
		del self.connection
		del self.xmms
		self.emit("service_connected", self.connected)

	def s_disconnect(self):
		# can't really disconnect, delete self.xmms?
		# must also delete self.connection
		pass

	### Service interface ###
	# TODO: service calls to check for error

	def playback_start(self, cb = None):
		self.xmms.playback_start(cb)

	def playback_stop(self, cb = None):
		self.xmms.playback_stop(cb)

	def playback_pause(self, cb = None):
		self.xmms.playback_pause(cb)

	def playback_play_pause_toggle(self):
		def __toggle_cb(res):
			if res.value() == xmmsclient.PLAYBACK_STATUS_PLAY:
				self.playback_pause()
			else:
				self.playback_start()			
		self.xmms.playback_status(__toggle_cb)

	def playback_status(self, cb):
		self.__run_value_cb(self.xmms.playback_status, cb)
	
	def playback_seek_ms(self, ms):
		self.xmms.playback_seek_ms(ms)
	
	def playlist_current_active(self, cb):
		def __cb(result):
			cb(result.value())
		self.xmms.playlist_current_active(__cb)
	
	def playlist_next(self):
		self.xmms.playlist_set_next_rel(1)
		self.xmms.playback_tickle()

	def playlist_prev(self):
		self.xmms.playlist_set_next_rel(-1)
		self.xmms.playback_tickle()

	def playlist_jump(self, pos):
		self.xmms.playlist_set_next(pos)
		self.xmms.playback_tickle()

	def playlist_play_pos(self, pos):
		def __status_check(res):
			if res.value() != xmmsclient.PLAYBACK_STATUS_PLAY:
				self.playback_start()
		self.xmms.playlist_set_next(pos)
		self.xmms.playback_tickle()
		self.xmms.playback_status(__status_check)

	def playlist_list_entries(self, cb, playlist=None):
		self.__pls_run_value_cb(self.xmms.playlist_list_entries, playlist, cb)

	def playlist_current_pos(self, cb, playlist=None):
		self.__pls_run_value_cb(self.xmms.playlist_current_pos, playlist, cb)

	def playlist_remove(self, pos):
		self.xmms.playlist_remove_entry(pos)

	def playlist_add_url(self, url):
		self.xmms.playlist_add_url(url)

	def playlist_add_from_library(self, id):
		self.xmms.playlist_add_id(int(id))

	def playlist_insert(self, pos, url):
		self.xmms.playlist_insert(pos, url)

	def playlist_insert_from_library(self, pos, id):
		self.xmms.playlist_insert_id(pos, int(id))
	
	def playlist_move(self, fromPos, toPos):
		self.xmms.playlist_move(fromPos, toPos)
	
	def playlist_shuffle(self):
		self.xmms.playlist_shuffle()
	
	def playlist_set_repeat(self, repeat):
		assert repeat in [Chalyx.PLAYLIST_REPEAT_NONE, Chalyx.PLAYLIST_REPEAT_ONE, Chalyx.PLAYLIST_REPEAT_ALL]
		
		local = {"setCount": 0}
		def __set_cb(res):
			if not res.iserror():
				local["setCount"] += 1
			if local["setCount"] == 2:
				self.thaw_signal("pl_repeat_change")
				self.emit("pl_repeat_change", repeat)
		self.freeze_signal("pl_repeat_change")
		if repeat == Chalyx.PLAYLIST_REPEAT_NONE:
			self.xmms.configval_set("playlist.repeat_one", "0", __set_cb)
			self.xmms.configval_set("playlist.repeat_all", "0", __set_cb)
		elif repeat == Chalyx.PLAYLIST_REPEAT_ONE:
			self.xmms.configval_set("playlist.repeat_one", "1", __set_cb)
			self.xmms.configval_set("playlist.repeat_all", "0", __set_cb)
		elif repeat == Chalyx.PLAYLIST_REPEAT_ALL:
			self.xmms.configval_set("playlist.repeat_one", "0", __set_cb)
			self.xmms.configval_set("playlist.repeat_all", "1", __set_cb)
	
	def __get_repeat(self):
		for key in ["playlist.repeat_one", "playlist.repeat_all"]:
			try:
				if self.__xmmsCfg[key][1] == 0:
					return -1
			except KeyError:
				return -1
		rptOne = bool(int(self.__xmmsCfg["playlist.repeat_one"][0]))
		rptAll = bool(int(self.__xmmsCfg["playlist.repeat_all"][0]))
		if not rptOne and not rptAll:
			return Chalyx.PLAYLIST_REPEAT_NONE
		elif rptOne and not rptAll:
			return Chalyx.PLAYLIST_REPEAT_ONE
		elif not rptOne and rptAll:
			return Chalyx.PLAYLIST_REPEAT_ALL
		elif rptOne and rptAll:
			# just guessing here, but this is a 'wtf' case
			return Chalyx.PLAYLIST_REPEAT_ONE
	
	def playlist_get_repeat(self, cb):
		def __got_rpt_check():
			rpt = self.__get_repeat()
			if rpt != -1:
				cb(rpt)
		def __rpt_one_cb(res):
			self.__xmmsCfg["playlist.repeat_one"] = [res.value(), 1]
			__got_rpt_check()
		def __rpt_all_cb(res):
			self.__xmmsCfg["playlist.repeat_all"] = [res.value(), 1]
			__got_rpt_check()
		# set the 'is up to date' flag on the config properties
		for key in ["playlist.repeat_one", "playlist.repeat_all"]:
			try:
				self.__xmmsCfg[key][1] = 0
			except KeyError:
				pass
		self.xmms.configval_get("playlist.repeat_one", __rpt_one_cb)
		self.xmms.configval_get("playlist.repeat_all", __rpt_all_cb)
	
	def playlist_list(self, cb):
		def __list_cb(res):
			cb(res.value())
		self.xmms.playlist_list(__list_cb)
	
	def playlist_load(self, playlist, cb):
		def __on_loaded(res):
			cb(res.value())
		self.xmms.playlist_load(playlist, __on_loaded)
	
	def library_get_info(self, id, cb):
		def __info_cb(res):
			cb(res.value())
		self.xmms.medialib_get_info(id, __info_cb)
	
	def library_find(self, query, cb):
		def __query_cb(res):
			cb(res.value())
		coll = xmmsclient.collections.coll_parse(query)
		self.xmms.coll_query_ids(coll, cb=__query_cb)
	
	def info_current_stream(self, cb):
		def __id_cb(res):
			self.library_get_info(res.value(), cb)
		self.xmms.playback_current_id(__id_cb)
	
	def bindata_add(self, id, data):
		# TODO: accept a callback arg to indicate success or failure?
		self.xmms.bindata_add(id, data)
	
	def bindata_remove(self, id):
		# TODO: accept a callback arg to indicate success or failure?
		self.xmms.bindata_remove(id)
	
	def bindata_retrieve(self, id, cb):
		def __data_cb(res):
			cb(res.value())
		self.xmms.bindata_retrieve(id, __data_cb)
	
	def __pls_run_value_cb(self, remote, pls, cb):
		def __value_cb(res):
			cb(res.value())
		remote(pls, __value_cb)
	
	def __run_value_cb(self, remote, cb):
		def __value_cb(res):
			cb(res.value())
		remote(__value_cb)

	### Proxied signals ###

	def _playtime(self, res):
		playtime = res.value()
		self.emit("pb_playtime", playtime)
		res.restart()

	def _pbstatus(self, res):
		self.emit("pb_status", res.value())

	def _curid(self, res):
		self.emit("pb_current_id", res.value())

	def _plpos(self, res):
		self.emit("pl_pos", res.value())

	def _plchg(self, res):
		self.emit("pl_change", res.value())
	
	def _plload(self, res):
		self.emit("pl_load", res.value())
	
	def _cfgchg(self, res):
		chg = res.value()
		rptChg = False
		for key, val in chg.items():
			self.__xmmsCfg[key] = [val, 1]
			if key in ["playlist.repeat_one", "playlist.repeat_all"]:
				rptChg = True
		if rptChg:
			rpt = self.__get_repeat()
			if rpt != -1:
				self.emit("pl_repeat_change", rpt)
		else:
			self.emit("config_change", chg)
